package cn.duwey.scs.common.base;

import cn.duwey.scs.common.util.BeanUtils;
import java.util.Date;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.jpa.repository.query.QueryUtils;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

@Slf4j
@Transactional(readOnly = true)
public class BaseRepositoryImpl<T extends BaseEntity, ID> extends
        SimpleJpaRepository<T, ID> implements BaseRepository<T, ID> {

    private final JpaEntityInformation<T, ?> entityInformation;
    private final EntityManager em;

    public BaseRepositoryImpl(JpaEntityInformation<T, ?> entityInformation,
            EntityManager entityManager) {

        super(entityInformation, entityManager);
        Assert.notNull(entityInformation, "JpaEntityInformation must not be null!");
        Assert.notNull(entityManager, "EntityManager must not be null!");
        this.entityInformation = entityInformation;
        this.em = entityManager;
    }

    @Override
    @Transactional
    public <S extends T> S save(S entity) {

        em.persist(entity);

        return entity;
    }

    @Override
    @Transactional
    public void update(T entity) {

        T originEntity = this.getOriginEntity(entity);

        BeanUtils.copyEntityProperties(entity, originEntity);

        em.merge(originEntity);

    }

    @Override
    @Transactional
    public void updateSelective(T entity) {

        T originEntity = this.getOriginEntity(entity);

        BeanUtils.copyEntityPropertiesIgnoreNull(entity, originEntity);

        em.merge(originEntity);

    }

    private T getOriginEntity(T entity) {

        Assert.notNull(entity, "The entity must not be null!");

        return this.getOriginEntityById((ID) entity.getId());
    }

    private T getOriginEntityById(ID id) {

        Assert.notNull(id, "The given id must not be null!");

        return this.findById(id).orElseThrow(() -> new EmptyResultDataAccessException(String
                .format("No %s entity with id %s exists!", this.entityInformation.getJavaType(),
                        id), 1));
    }

    @Override
    @Transactional
    public void deleteById(ID id) {

        T entity = this.getOriginEntityById(id);

        entity.setIsDeleted(1);

        this.em.merge(entity);
    }

    @Override
    @Transactional
    public void delete(T entity) {

        this.deleteById((ID) entity.getId());
    }

    @Override
    @Transactional
    public void deleteInBatch(Iterable<T> entities) {

        super.deleteAll(entities);
    }

    @Override
    @Transactional
    public void deleteAllInBatch() {

        String jpql = QueryUtils.getQueryString(
                "update %s x set x.isDeleted=1, x.rowVersion=x.rowVersion+1, x.modifiedBy=:modifiedBy, x.modifiedDate=:modifiedDate where x.isDeleted=0",
                this.entityInformation.getEntityName());

        Query query = this.em.createQuery(jpql);

        query.setParameter("modifiedBy", "-1");
        query.setParameter("modifiedDate", new Date());

        query.executeUpdate();
    }

    @Override
    public T getOne(ID id) {

        return super.findById(id).orElse(null);
    }

}
